﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;

namespace eProject.Model
{

    public class ActivitiesManager
    {
        private readonly Dictionary<Activity, double> _activities = new Dictionary<Activity, double>();

        private readonly Dictionary<Activity, IList<double>> _details = new Dictionary<Activity, IList<double>>();

        private readonly Schedule _schedule;

        private readonly Storage _commonStorage;

        public delegate void ManHoursChangedEventHandler(IEnumerable<Activity> activities);

        public event ManHoursChangedEventHandler ManHoursChanged;

        public ActivitiesManager(Storage commonStorage, Schedule schedule)
        {
            if (schedule == null) throw new ArgumentNullException("schedule");
            if (commonStorage == null) throw new ArgumentNullException("commonStorage");
            _schedule = schedule;
            _commonStorage = commonStorage;
            _commonStorage.Activities.CollectionChanged += ActivitiesCollectionChangedEventHandler;
            _schedule.IndividualRulesChanged += delegate(IEnumerable<Employee> employees)
                                                    {
                                                        foreach (var employee in employees)
                                                        {
                                                            RecalculateAll(employee);
                                                        }
                                                    };
            _commonStorage.ScheduleSettings.SettingsChanged += delegate{RecalculateAll();};

            LoadFromStore();
        }       

        #region Methods      
      
        public double GetManHours(Activity activity)
        {
            return _activities[activity];//TODO Cheking if if _activities doesn't contain the activity
        }

        public double GetManHours(Task task)
        {
            var acts = GetActivities(task);
            return acts.Sum(a => _activities[a]);
        }

        public IEnumerable<double> GetDetails(Activity activity)
        {
            return _details[activity];//TODO optimize it
        }

        #endregion

        #region Private Helpers 

        private void LoadFromStore()
        {
            var acts = _commonStorage.Activities;
            foreach (var act in acts)
            {
                _activities.Add(act, 0);
            }
            RecalculateAll();
        }

        private void RecalculateAll()
        {
            var grpacts = _activities.Keys.GroupBy(a => a.Employee);
            foreach (var grpact in grpacts)
            {
                RecalculateActivities(grpact);
            }         
        }

        private void RecalculateAll(Employee employee)
        {
            var acts = _activities.Keys.Where(a => a.Employee == employee).ToList();
            RecalculateActivities(acts);
        }
    
        private IEnumerable<Activity> GetActivities(Employee employee)
        {
            var activities = from act in _commonStorage.Activities
                             where act.Employee == employee
                             select act;
            return activities.ToList();
        }

        private IEnumerable<Activity> GetActivities(Task task)
        {
            var activities = from act in _commonStorage.Activities
                             where act.Task == task
                             select act;
            return activities.ToList();
        }   

        private static IEnumerable<Activity> GetByPeriod(IEnumerable<Activity> activities, Period period)
        {
            var result = from act in activities
                         where act.Period.Begin < period.End && act.Period.End > period.Begin
                         select act;
            return result.ToList();
        }

        private static Period CalculatePeriod(IEnumerable<Activity> activities, Period period)
        {
            var acts = from act in activities
                       where period.Begin < act.Period.End && period.Begin > act.Period.Begin
                       select act;
            if (acts.Count() > 0)
            {
                var newBegin = acts.Min(a => a.Period.Begin);
                return CalculatePeriod(activities, new Period(newBegin, period.End));
            }

            acts = from act in activities
                   where period.End > act.Period.Begin && period.End < act.Period.End
                   select act;
            if (acts.Count() > 0)
            {
                var newEnd = acts.Max(a => a.Period.End);
                return CalculatePeriod(activities, new Period(period.Begin, newEnd));
            }
            return period;
        }

        private static IList<Period> GetIntersections(IEnumerable<Activity> activities)
        {
            var intscs = (from act in activities
                          select act.Period.Begin).Union(from act in activities
                                                         select act.Period.End).Distinct().OrderBy(d => d.Ticks).ToList();
            var periods = new List<Period>(intscs.Count);
            for (var i = 0; i < intscs.Count - 1; i++)
            {
                var p = new Period(intscs[i], intscs[i + 1]);
                periods.Add(p);
            }
            return periods;
        }

        private void RecalculateActivities(IEnumerable<Activity> activities)
        {

            foreach (var activity in activities)
            {
                _activities[activity] = 0;
                _details[activity].Clear();
            }

            var intersections = GetIntersections(activities);
                        
            foreach (var period in intersections)
            {
                var acts = GetByPeriod(activities, period);
                var sum = acts.Sum(a => a.Priority);

                foreach (var act in acts)
                {
                    var dt = period.Begin.Date;
                    var end = period.End.Date;
                    while (dt < end)
                    {
                        var manHours = (((double)act.Priority) / sum) * _schedule.GetManHours(act.Employee, dt);
                        _activities[act] += manHours;    
                        _details[act].Add(manHours);
                        dt = dt.AddDays(1);
                    }                                        
                }
            }

            InvokeManHoursChanged(activities);
        }

        private void Recalculate(Employee employee, Period period)//TODO public
        {
            var acts = GetActivities(employee);            
            var p = CalculatePeriod(acts, period);
            var result = GetByPeriod(acts, p);
            RecalculateActivities(result);
        }

        private void InvokeManHoursChanged(IEnumerable<Activity> activities)
        {
            var changed = ManHoursChanged;
            if (changed != null) changed(activities);
        }

        private void ActivitiesCollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs args)
        {
            if (args.NewItems != null)
            {
                var newItems = args.NewItems.Cast<Activity>();
                foreach (var item in newItems)
                {
                    _activities.Add(item,0);
                    _details.Add(item,new List<double>((int)item.Period.Duration));
                    item.PeriodChanged += ActivityPeriodChangerEventHandler;
                    item.PriorityChanged += ActivityPriorityChangerEventHandler;
                    Recalculate(item.Employee, item.Period);
                }                
            }
            if (args.OldItems != null)
            {
                var oldItems = args.OldItems.Cast<Activity>();
                foreach (var item in oldItems)
                {
                    item.PeriodChanged -= ActivityPeriodChangerEventHandler;
                    item.PriorityChanged -= ActivityPriorityChangerEventHandler;
                    _activities.Remove(item);
                    _details.Remove(item);
                    Recalculate(item.Employee, item.Period);
                }
            }
        }

        private void ActivityPeriodChangerEventHandler(Activity activity, Period oldPeriod)
        {
            Recalculate(activity.Employee, oldPeriod.Union(activity.Period));
        }

        private void ActivityPriorityChangerEventHandler(Activity activity)
        {
            Recalculate(activity.Employee, activity.Period);
        }        

        #endregion

    }
}
